/* Copyright (C) 2004 - 2011  Versant Inc.  http://www.db4o.com */

using System;
using System.Collections;
using Db4objects.Db4o.Foundation;
using Db4objects.Db4o.Internal;
using Db4objects.Db4o.Qlin;
using Db4objects.Db4o.Reflect;
using Db4objects.Db4o.Reflect.Core;
using Db4objects.Db4o.Reflect.Generic;

namespace Db4objects.Db4o.Qlin
{
	/// <summary>creates prototype objects for classes.</summary>
	/// <remarks>
	/// creates prototype objects for classes. Each field on prototype objects is set
	/// to a newly created object or primitive that can be identified either by it's
	/// identity or by an int ID that is generated by the system. Creation of fields
	/// is recursed to the depth specified in the constructor.<br />
	/// <br />
	/// Allows analyzing expressions called on prototype objects to find the
	/// underlying field that delivers the return value of the expression. Passed
	/// expressions should not have side effects on objects, otherwise the
	/// "prototype world" will no longer work.<br />
	/// <br />
	/// We plan to supply an ImmutableFieldClassLoader to instrument the code to
	/// throw on every modification. This ClassLoader could also supply information
	/// about all the method calls involved.<br />
	/// <br />
	/// For now our approach only works if expressions are directly backed by a
	/// single field.<br />
	/// <br />
	/// We were inspired for this approach when we saw that Thomas Mueller managed to
	/// map expressions to fields for his JaQu query interface, Kudos!
	/// http://www.h2database.com/html/jaqu.html<br />
	/// <br />
	/// We took the idea a bit further and made it work for all primitives except for
	/// boolean and we plan to also get deeper expressions, collections and
	/// interfaces working nicely.
	/// </remarks>
	public class Prototypes
	{
		private readonly IReflector _reflector;

		private readonly Hashtable4 _prototypes = new Hashtable4();

		private readonly bool _ignoreTransient;

		private readonly int _recursionDepth;

		public Prototypes(IReflector reflector, int recursionDepth, bool ignoreTransient)
		{
			_reflector = reflector;
			_recursionDepth = recursionDepth;
			_ignoreTransient = ignoreTransient;
		}

		public Prototypes() : this(DefaultReflector(), 5, false)
		{
		}

		/// <summary>returns a prototype object for a specific class.</summary>
		/// <remarks>returns a prototype object for a specific class.</remarks>
		public virtual object PrototypeForClass(Type clazz)
		{
			if (clazz == null)
			{
				throw new PrototypesException("Class can not be null");
			}
			IReflectClass claxx = _reflector.ForClass(clazz);
			if (claxx == null)
			{
				throw new PrototypesException("Not found in the reflector: " + clazz);
			}
			string className = claxx.GetName();
			Prototypes.Prototype prototype = (Prototypes.Prototype)_prototypes.Get(className);
			if (prototype != null)
			{
				return prototype.Object();
			}
			prototype = new Prototypes.Prototype(this, claxx);
			_prototypes.Put(className, prototype);
			return prototype.Object();
		}

		/// <summary>
		/// analyzes the passed expression and tries to find the path to the
		/// backing field that is accessed.
		/// </summary>
		/// <remarks>
		/// analyzes the passed expression and tries to find the path to the
		/// backing field that is accessed.
		/// </remarks>
		public virtual IEnumerator BackingFieldPath(Type clazz, object expression)
		{
			return BackingFieldPath(_reflector.ForClass(clazz), expression);
		}

		/// <summary>
		/// analyzes the passed expression and tries to find the path to the
		/// backing field that is accessed.
		/// </summary>
		/// <remarks>
		/// analyzes the passed expression and tries to find the path to the
		/// backing field that is accessed.
		/// </remarks>
		public virtual IEnumerator BackingFieldPath(IReflectClass claxx, object expression
			)
		{
			return BackingFieldPath(claxx.GetName(), expression);
		}

		/// <summary>
		/// analyzes the passed expression and tries to find the path to the
		/// backing field that is accessed.
		/// </summary>
		/// <remarks>
		/// analyzes the passed expression and tries to find the path to the
		/// backing field that is accessed.
		/// </remarks>
		public virtual IEnumerator BackingFieldPath(string className, object expression)
		{
			Prototypes.Prototype prototype = (Prototypes.Prototype)_prototypes.Get(className);
			if (prototype == null)
			{
				return null;
			}
			return prototype.BackingFieldPath(_reflector, expression);
		}

		private class Prototype
		{
			private readonly IdentityHashtable4 _fieldsByIdentity = new IdentityHashtable4();

			private readonly Hashtable4 _fieldsByIntId = new Hashtable4();

			private readonly object _object;

			private int intIdGenerator;

			public Prototype(Prototypes _enclosing, IReflectClass claxx)
			{
				this._enclosing = _enclosing;
				this._object = (object)claxx.NewInstance();
				if (this._object == null)
				{
					throw new PrototypesException("Prototype could not be created for class " + claxx
						.GetName());
				}
				this.Analyze(this._object, claxx, this._enclosing._recursionDepth, null);
			}

			private void Analyze(object @object, IReflectClass claxx, int depth, List4 parentPath
				)
			{
				if (depth < 0)
				{
					return;
				}
				ReflectorUtils.ForEachField(claxx, new _IProcedure4_130(this, parentPath, claxx, 
					@object, depth));
			}

			private sealed class _IProcedure4_130 : IProcedure4
			{
				public _IProcedure4_130(Prototype _enclosing, List4 parentPath, IReflectClass claxx
					, object @object, int depth)
				{
					this._enclosing = _enclosing;
					this.parentPath = parentPath;
					this.claxx = claxx;
					this.@object = @object;
					this.depth = depth;
				}

				public void Apply(object field)
				{
					if (((IReflectField)field).IsStatic())
					{
						return;
					}
					if (this._enclosing._enclosing._ignoreTransient && ((IReflectField)field).IsTransient
						())
					{
						return;
					}
					IReflectClass fieldType = ((IReflectField)field).GetFieldType();
					List4 path = new List4(parentPath, ((IReflectField)field));
					Prototypes.IntegerConverter converter = Prototypes.IntegerConverterforClassName(claxx
						.Reflector(), fieldType.GetName());
					if (converter != null)
					{
						int id = ++this._enclosing.intIdGenerator;
						object integerRepresentation = converter.FromInteger(id);
						if (!Prototypes.TrySetField(((IReflectField)field), @object, integerRepresentation
							))
						{
							return;
						}
						this._enclosing._fieldsByIntId.Put(id, new Pair(integerRepresentation, path));
						return;
					}
					if (!fieldType.IsPrimitive())
					{
						object identityInstance = fieldType.NewInstance();
						if (identityInstance == null)
						{
							return;
						}
						if (!Prototypes.TrySetField(((IReflectField)field), @object, identityInstance))
						{
							return;
						}
						this._enclosing._fieldsByIdentity.Put(identityInstance, path);
						this._enclosing.Analyze(identityInstance, claxx, depth - 1, path);
					}
				}

				private readonly Prototype _enclosing;

				private readonly List4 parentPath;

				private readonly IReflectClass claxx;

				private readonly object @object;

				private readonly int depth;
			}

			public virtual object Object()
			{
				return this._object;
			}

			public virtual IEnumerator BackingFieldPath(IReflector reflector, object expression
				)
			{
				if (expression == null)
				{
					return null;
				}
				IReflectClass claxx = reflector.ForObject(expression);
				if (claxx == null)
				{
					return null;
				}
				Prototypes.IntegerConverter converter = Prototypes.IntegerConverterforClassName(reflector
					, claxx.GetName());
				if (converter != null)
				{
					Pair entry = (Pair)this._fieldsByIntId.Get(converter.ToInteger(expression));
					if (entry == null)
					{
						return null;
					}
					if (entry.first.Equals(expression))
					{
						return this.AsIterator((List4)entry.second);
					}
					return null;
				}
				if (claxx.IsPrimitive())
				{
					return null;
				}
				return this.AsIterator((List4)this._fieldsByIdentity.Get(expression));
			}

			private IEnumerator AsIterator(List4 lastElement)
			{
				return Iterators.Revert(Iterators.Map(Iterators.Iterate(lastElement), new _IFunction4_198
					()));
			}

			private sealed class _IFunction4_198 : IFunction4
			{
				public _IFunction4_198()
				{
				}

				public object Apply(object field)
				{
					return ((IReflectField)field).GetName();
				}
			}

			private readonly Prototypes _enclosing;
		}

		private static Prototypes.IntegerConverter IntegerConverterforClassName(IReflector
			 reflector, string className)
		{
			if (_integerConverters == null)
			{
				_integerConverters = new Hashtable4();
				Prototypes.IntegerConverter[] converters = new Prototypes.IntegerConverter[] { new 
					_IntegerConverter_211(), new _IntegerConverter_215(), new _IntegerConverter_219(
					), new _IntegerConverter_223(), new _IntegerConverter_227(), new _IntegerConverter_231
					(), new _IntegerConverter_235(), new _IntegerConverter_239() };
				for (int converterIndex = 0; converterIndex < converters.Length; ++converterIndex)
				{
					Prototypes.IntegerConverter converter = converters[converterIndex];
					_integerConverters.Put(converter.PrimitiveName(), converter);
					if (!converter.PrimitiveName().Equals(converter.WrapperName(reflector)))
					{
						_integerConverters.Put(converter.WrapperName(reflector), converter);
					}
				}
			}
			return (Prototypes.IntegerConverter)_integerConverters.Get(className);
		}

		private sealed class _IntegerConverter_211 : Prototypes.IntegerConverter
		{
			public _IntegerConverter_211()
			{
			}

			public override string PrimitiveName()
			{
				return typeof(int).FullName;
			}

			public override object FromInteger(int i)
			{
				return i;
			}
		}

		private sealed class _IntegerConverter_215 : Prototypes.IntegerConverter
		{
			public _IntegerConverter_215()
			{
			}

			public override string PrimitiveName()
			{
				return typeof(long).FullName;
			}

			public override object FromInteger(int i)
			{
				return System.Convert.ToInt64(i);
			}
		}

		private sealed class _IntegerConverter_219 : Prototypes.IntegerConverter
		{
			public _IntegerConverter_219()
			{
			}

			public override string PrimitiveName()
			{
				return typeof(double).FullName;
			}

			public override object FromInteger(int i)
			{
				return System.Convert.ToDouble(i);
			}
		}

		private sealed class _IntegerConverter_223 : Prototypes.IntegerConverter
		{
			public _IntegerConverter_223()
			{
			}

			public override string PrimitiveName()
			{
				return typeof(float).FullName;
			}

			public override object FromInteger(int i)
			{
				return System.Convert.ToSingle(i);
			}
		}

		private sealed class _IntegerConverter_227 : Prototypes.IntegerConverter
		{
			public _IntegerConverter_227()
			{
			}

			public override string PrimitiveName()
			{
				return typeof(byte).FullName;
			}

			public override object FromInteger(int i)
			{
				return (byte)i;
			}
		}

		private sealed class _IntegerConverter_231 : Prototypes.IntegerConverter
		{
			public _IntegerConverter_231()
			{
			}

			public override string PrimitiveName()
			{
				return typeof(char).FullName;
			}

			public override object FromInteger(int i)
			{
				return (char)i;
			}
		}

		private sealed class _IntegerConverter_235 : Prototypes.IntegerConverter
		{
			public _IntegerConverter_235()
			{
			}

			public override string PrimitiveName()
			{
				return typeof(short).FullName;
			}

			public override object FromInteger(int i)
			{
				return (short)i;
			}
		}

		private sealed class _IntegerConverter_239 : Prototypes.IntegerConverter
		{
			public _IntegerConverter_239()
			{
			}

			public override string PrimitiveName()
			{
				return typeof(string).FullName;
			}

			public override object FromInteger(int i)
			{
				return Prototypes.StringIdentifier + i;
			}

			public override int ToInteger(object obj)
			{
				if (!(obj is string))
				{
					return -1;
				}
				string str = (string)obj;
				if (str.Length < Prototypes.StringIdentifier.Length)
				{
					return -1;
				}
				if (str.IndexOf(Prototypes.StringIdentifier) != 0)
				{
					return -1;
				}
				return int.Parse(Sharpen.Runtime.Substring(str, Prototypes.StringIdentifier.Length
					));
			}
		}

		private static Hashtable4 _integerConverters;

		private abstract class IntegerConverter
		{
			public virtual string WrapperName(IReflector reflector)
			{
				return reflector.ForObject(FromInteger(1)).GetName();
			}

			public abstract string PrimitiveName();

			public abstract object FromInteger(int i);

			public virtual int ToInteger(object obj)
			{
				return int.Parse(((object)obj).ToString());
			}
		}

		private static readonly string StringIdentifier = "QLinIdentity";

		// Strings get prepended the following, so we can also use strings 
		// without restrictions in queries.
		public virtual IReflector Reflector()
		{
			return _reflector;
		}

		// We could always use this, but we want to make users of this class
		// aware that they have control over the reflector and that it is
		// important.
		public static IReflector DefaultReflector()
		{
			return new GenericReflector(Platform4.ReflectorForType(typeof(Prototypes)));
		}

		private static bool TrySetField(IReflectField field, object onObject, object value
			)
		{
			try
			{
				field.Set(onObject, value);
			}
			catch
			{
				return false;
			}
			return true;
		}
	}
}
